package com.one.blocks.rbac.manager;

import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.one.blocks.mvc.manager.AbstractBatchManager;
import com.one.blocks.mvc.util.ScopeWebRequestHelper;
import com.one.blocks.mvc.valid.validator.AllocatedDataManager;
import com.one.blocks.rbac.constant.RbacConstant.MenuCache;
import com.one.blocks.rbac.domain.SysMenu;
import com.one.blocks.rbac.domain.SysRole;
import com.one.blocks.rbac.domain.SysRoleMenu;
import com.one.blocks.rbac.enums.RoleTypeEnum;
import com.one.blocks.rbac.mapper.SysMenuMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.ConstraintValidatorContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static com.one.blocks.rbac.constant.RbacConstant.ScopeRequest.ROLES_OF_USER_ID;
import static java.util.stream.Collectors.toCollection;

/**
 * @author <a href="mailto:idler41@163.con">idler41</a> created on 2022-02-14 13:57:56
 */
@Service
public class SysMenuManager extends AbstractBatchManager<SysMenuMapper, SysMenu> implements AllocatedDataManager<Integer[]> {

    @Autowired
    private SysRoleManager sysRoleManager;

    @Autowired
    private SysRoleMenuManager sysRoleMenuManager;

    @Autowired
    private SysMenuManager sysMenuManager;

    @Autowired
    public SysMenuManager(SysMenuMapper mapper) {
        super(mapper);
    }

    @Cached(area = MenuCache.AREA, name = MenuCache.NAME, key = MenuCache.PERMS_ROLE_ID + "+args[0]", cacheType = CacheType.BOTH)
    public Set<String> findPermissionByRole(Integer roleId) {
        return mapper.selectPermission(new Integer[]{roleId});
    }

    @Cached(area = MenuCache.AREA, name = MenuCache.NAME, key = MenuCache.MENU_ROLE_ID + "+args[0]", cacheType = CacheType.BOTH)
    public List<SysMenu> findByRoleId(Integer roleId) {
        // 超级管理员查询所有
        SysRole sysRole = sysRoleManager.get(roleId);
        if (RoleTypeEnum.ADMIN.match(sysRole.getType())) {
            return mapper.selectAllOfOrder();
        }
        return mapper.selectByRoleId(roleId);
    }

    public Set<String> findPermissionByUser(Integer userId) {
        List<SysRole> sysRoleList = ScopeWebRequestHelper.get(ROLES_OF_USER_ID + userId,
                () -> sysRoleManager.findByUserId(userId));
        if (CollectionUtils.isEmpty(sysRoleList)) {
            return Collections.emptySet();
        }
        // 超级管理员查询所有
        if (sysRoleList.stream().anyMatch(i -> RoleTypeEnum.ADMIN.match(i.getType()))) {
            // 超级管理员返回*
            return Collections.singleton("*");
        }
        // 查询指定角色权限
        return mapper.selectPermission((sysRoleList.stream().map(SysRole::getId).toArray(Integer[]::new)));
    }

    public List<SysMenu> findTreeByUser(Integer userId) {
        List<SysRole> sysRoleList = ScopeWebRequestHelper.get(ROLES_OF_USER_ID + userId,
                () -> sysRoleManager.findByUserId(userId));
        if (CollectionUtils.isEmpty(sysRoleList)) {
            return Collections.emptyList();
        }
        // 超级管理员查询所有
        if (sysRoleList.stream().anyMatch(i -> RoleTypeEnum.ADMIN.match(i.getType()))) {
            return mapper.selectAllOfOrder();
        }
        List<SysMenu> result = new ArrayList<>();
        for (SysRole sysRole : sysRoleList) {
            result.addAll(sysMenuManager.findByRoleId(sysRole.getId()));
        }
        // 去重+排序后返回 TODO 测试验证
        if (sysRoleList.size() > 1) {
            result = result
                    .stream()
                    // 原集合顺序打乱，但后续会重新排序
                    .collect(Collectors.collectingAndThen(
                            toCollection(() -> new TreeSet<>(Comparator.comparing(SysMenu::getId))), ArrayList::new)
                    )
                    .stream()
                    .sorted(Comparator
                            .comparing(SysMenu::getLevelNum, Comparator.nullsLast(Integer::compareTo))
                            .thenComparing(SysMenu::getOrderNum, Comparator.nullsLast(Integer::compareTo)))
                    .collect(Collectors.toList());
        }
        return result;
    }

    @CacheInvalidate(area = MenuCache.AREA, name = MenuCache.NAME, key = MenuCache.PERMS_ROLE_ID + "+args[0]")
    @CacheInvalidate(area = MenuCache.AREA, name = MenuCache.NAME, key = MenuCache.MENU_ROLE_ID + "+args[0]")
    @Transactional(rollbackFor = Exception.class)
    public void grant(Integer roleId, List<SysRoleMenu> sysRoleMenus) {
        sysRoleMenuManager.deleteByRoleId(roleId);
        sysRoleMenuManager.insertBatch(sysRoleMenus);
    }

    @Override
    public boolean findAllocatedData(Integer[] menuIds, ConstraintValidatorContext context) {
        return sysRoleMenuManager.findOneByMenuIds(menuIds) != null;
    }

    public List<SysMenu> findAllOfOrder() {
        return mapper.selectAllOfOrder();
    }
}